package com.dyna.auth.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import com.dyna.api.domain.dto.UserLoginInfo;
import com.dyna.api.domain.req.LoginEntity;
import com.dyna.api.domain.resp.AuthLoginResp;
import com.dyna.api.event.LoginUserEvent;
import com.dyna.auth.service.LoginService;
import com.dyna.auth.service.TokenService;
import com.dyna.constants.*;
import com.dyna.core.exception.CaptchaException;
import com.dyna.core.exception.CustomException;
import com.dyna.core.utils.ObjectUtil;
import com.dyna.core.utils.SecurityEncodeUtil;
import com.dyna.domain.dto.KeyValue;
import com.dyna.domain.dto.LoginUser;
import com.dyna.domain.resp.R;
import com.dyna.enums.CommonStatusEnum;
import com.dyna.enums.LoginStatusEnum;
import com.dyna.foundation.domain.entity.SysUser;
import com.dyna.foundation.feign.FoundationUserApi;
import com.dyna.utils.RdsUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * @author zhoucaiwang
 * @date 2023/8/15
 */

@Slf4j
@Service
public class LoginServiceImpl implements LoginService {

    private static final String GET_LOGIN_USERINFO_ERROR = "获取登录用户信息失败";

    @Value("${auth.captcha.enable}")
    private Boolean captchaEnable;

    @Resource
    private FoundationUserApi foundationUserApi;
    @Resource
    private ApplicationEventPublisher applicationEventPublisher;
    @Resource
    private TokenService tokenService;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public AuthLoginResp login(LoginEntity login) {
        // 验证码是否已过期
        this.checkCaptchaCode(login);
        // 从缓存查询登录次数
        KeyValue<String, String> result = doLoginErrorCount(login.getUsername());
        // 验证密码
        LoginUser loginUser = authenticate(login.getUsername(), login.getPassword());
        // 创建token
        AuthLoginResp authLoginResp = doCreateAccessToken(loginUser, result);
        // 清掉用户登录错误次数
        if (StrUtil.isNotBlank(result.getValue())) {
            RdsUtil.del(result.getKey());
        }
        return authLoginResp;
    }

    /**
     * 创建token
     */
    private AuthLoginResp doCreateAccessToken(LoginUser loginUser, KeyValue<String, String> result) {
        // 创建访问令牌
        AuthLoginResp accessTokenDO = tokenService.createAccessToken(loginUser.getUserId(), OAuth2ClientConstants.CLIENT_ID_DEFAULT, null);
        // 使用事件驱动将登录日志保存到es中
        applicationEventPublisher.publishEvent(
                new LoginUserEvent(
                        new UserLoginInfo(loginUser.getUsername(), loginUser.getToken(), LoginStatusEnum.LOGIN.getStatus()),
                        loginUser
                ));
        return accessTokenDO;
    }

    private static KeyValue<String, String> doLoginErrorCount(String username) {
        String errorNumKey = RedisKeyConstant.LOGIN_ERROR_NUM_KEY + username;
        String errorCount = RdsUtil.get(errorNumKey);
        if (StrUtil.isNotBlank(errorCount)) {
            Integer anInt = Convert.toInt(errorCount);
            if (3 == anInt) {
                throw new CustomException("请10分钟后再次尝试");
            } else if (4 == anInt) {
                throw new CustomException("请30分钟后再次尝试");
            } else if (anInt > 4) {
                throw new CustomException("您尝试登录次数过多，请您次日重新登陆");
            }
        }
        return new KeyValue<>(errorNumKey, errorCount);
    }

    @Override
    public LoginUser authenticate(String username, String password) {
        R<SysUser> userR = foundationUserApi.getUserByUsername(username);
        if (userR.isSuccess() && ObjectUtil.isNull(userR.getData())) {
            throw new CustomException(ModuleErrorCodeConstants.USER_NOT_EXISTS.getMsg());
        }
        if (!SecurityEncodeUtil.matches(password, userR.getData().getPassword())) {
            // 记录错误次数
            RdsUtil.inc(RedisKeyConstant.LOGIN_ERROR_NUM_KEY + username, TokenConstant.ERROR_EXPIRE_TIME, TimeUnit.HOURS);
            throw new CustomException("密码不正确");
        }
        // 弱密码校验
        if (SecurityEncodeUtil.weakPassword(password)) {
            throw new CustomException("您当前登录密码为弱密码，请重新修改密码");
        }
        // 是否被禁用
        if (CommonStatusEnum.DISABLE.getStatus().equals(userR.getData().getStatus())) {
            throw new CustomException("账号已被禁用");
        }
        return BeanUtil.copyProperties(userR.getData(), LoginUser.class);
    }

    /**
     * 校验验证码
     */
    private void checkCaptchaCode(LoginEntity login) {
        // false关闭验证码
        if (!captchaEnable) {
            return;
        }
        // 校验验证码是否一致
        String codeKey = RedisKeyConstant.CAPTCHA_PRE_KEY + login.getUuid();
        String cacheCode = RdsUtil.get(codeKey);
        if (StrUtil.isBlank(cacheCode)) {
            throw new CaptchaException("请重新获取验证码");
        }
        // 验证码校验通过，删除验证码
        RdsUtil.del(codeKey);
        // 验证码是否相同
        String code = login.getCode().toUpperCase();
        if (!cacheCode.split(StringPool.DASH)[0].equals(code)) {
            Long inc = RdsUtil.inc(RedisKeyConstant.LOGIN_ERROR_NUM_KEY + login.getUsername(), TokenConstant.ERROR_EXPIRE_TIME, TimeUnit.HOURS);
            log.info("用户登录次数:::{}", inc);
            throw new CustomException("验证码不正确");
        }
    }

    @Override
    public void logout(String token, Integer logType) {
        // 删除访问令牌
        tokenService.removeAccessToken(token);
    }

    @Override
    public AuthLoginResp refreshToken(String refreshToken) {
        AuthLoginResp accessTokenDO = tokenService.refreshAccessToken(refreshToken, OAuth2ClientConstants.CLIENT_ID_DEFAULT);
        return accessTokenDO;
    }

}